import React from 'react';
import { FloatButton } from 'antd'
import EventEmitter from 'eventemitter3'
import _ from 'lodash';
import { QWebChannel } from './qwebchannel'
import { assert, __DEV__ } from '../util/utils'
import { ERROR_CODE } from '../core/common'

let qWebChannel = null;
const requestQueue = []

export const EE = new EventEmitter();

export const isQtClient = (function () {
  return navigator.userAgent.includes('QtWebEngine')
})();

class SparkError extends Error {
  constructor(message, code, msg) {
    super(message);
    this.code = code;
    this.msg = msg;
  }
}

function checkErrorCode(resp) {
  let err;
  if (!_.isObject(resp)) {
    err = new SparkError("resp not object");
    err.response = resp;
  }

  let { code, message } = resp;
  if (code !== ERROR_CODE.SUCCESS) {
    err = new SparkError('api error: ' + message, code, message);
    err.response = resp;
  }

  return err;
}

const defaultHandler = (response) => {
  console.info(`signal emited from Qt: ${JSON.stringify(response)}`);
};

function QWC(mockType) {
  if (!__DEV__) {
    assert(
      window && window.qt && window.qt.webChannelTransport,
      "'qt' or 'qt.webChannelTransport' should be initialized(injected) by QtWebEngine"
    )
  }
  return new Promise((resolve, reject) => {
    if (!qWebChannel) {
      console.log('create web channel.');
      qWebChannel = new QWebChannel(window.qt.webChannelTransport, (channel) => {
        console.log('QWebChannel setup')
        const qtContext = channel.objects.spark;

        qWebChannel.__apis__ = {};
        for (const i in qtContext) {
          const v = qtContext[i];

          if (i.endsWith("_changed") && typeof v === 'object' && typeof v.connect === 'function') {
            console.info(`${i} is a signal`);
            v.connect(defaultHandler);
          }

          if (typeof v === 'function' && i.startsWith("request_")) {
            console.info(`${i} is a request slot`);
            qWebChannel.__apis__[i] = (...request) => {
              return new Promise((resolve, reject) => {
                console.log('>>>', i, ...request);
                v(...request, (resp) => {
                  console.log('<<<', i, resp);
                  const err = checkErrorCode(resp);
                  if (err) {
                    reject(err);
                  } else {
                    resolve(resp);
                  }
                });
              });
            };
          }
        }

        qWebChannel.__apis__.context = qtContext;
        resolve(qWebChannel.__apis__)
        let p;
        while ((p = requestQueue.shift())) {
          p.resolve(qWebChannel.__apis__)
        }
      })
      window.qWebChannel = qWebChannel;
    } else {
      if (qWebChannel.__apis__) {
        resolve(qWebChannel.__apis__)
      } else {
        requestQueue.push({ resolve, reject })
      }
    }
  })
}

// 用于在浏览器端开发时，模拟 `Qt` 的注入行为
export function mockWebChannelApi(mock_module) {
  const transport = {
    send(data) {
      // do nothing
    }
  };
  qWebChannel = new QWebChannel(transport);
  console.log(`QWebChannel simulator activated !`)

  mock_module.then(module => {
    let mock = module.mock;
    qWebChannel.__apis__ = {};
    const context = {};
    qWebChannel.__apis__.context = context;

    // 仿真数据映射
    const { contexts } = mock;
    for (const i in contexts) {
      context[i] = contexts[i];
    }

    // 仿真信号
    const { signals } = mock;
    for (const i in signals) {
      console.info(`${i} is a signal`);
      context[i] = { // 仿真信号的connect函数
        connect: (f) => {
          EE.on(i, f);
          EE.on(i, defaultHandler);
        }
      };

      qWebChannel.__apis__[`emit_${i}`] = () => { // 仿真信号的connect函数
        EE.emit(i, signals[i]);
      };
    }

    const signalDebug = !mock.showTrigger ? null :
      <FloatButton.Group
        shape="circle"
        trigger="hover"
        type="primary">
        {
          Object.keys(signals).map(i =>
            <FloatButton key={i} tooltip={<div>EMIT {i}</div>} onClick={() => {
              EE.emit(i, signals[i]);
            }} />
          )
        }
      </FloatButton.Group>;

    qWebChannel.__apis__.signalDebug = signalDebug;

    // 仿真槽
    const { slots } = mock;
    for (const i in slots) {
      console.info(`${i} is a request slot`);
      const m = slots[i];
      qWebChannel.__apis__[i] = async (...request) => {
        const handler = (typeof m.response) === 'function'
          ? m.response
          : () => m.response;
        const delay = m.delay || 0;

        return new Promise((resolve, reject) => {
          setTimeout(() => {
            console.log('>>>', i, ...request);
            const resp = handler(...request);
            console.log('<<<', i, resp);
            const err = checkErrorCode(resp);
            if (err) {
              reject(err);
            } else {
              resolve(resp);
            }
          }, delay);
        });
      }
    }

    let p;
    while ((p = requestQueue.shift())) {
      p.resolve(qWebChannel.__apis__)
    }
  });
}

export default QWC;